/*
 *
 * Copyright (c) 2007, Sun Microsystems, Inc.
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *  * Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *  * Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *  * Neither the name of Sun Microsystems nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package app_files.bluetooth.server;

import app_files.MediaNet;
import app_files.files.File;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import java.util.Hashtable;
import java.util.Vector;

// jsr082 API
import javax.bluetooth.DataElement;
import javax.bluetooth.DiscoveryAgent;
import javax.bluetooth.LocalDevice;
import javax.bluetooth.ServiceRecord;
import javax.bluetooth.ServiceRegistrationException;
import javax.bluetooth.UUID;

// midp/cldc API
import javax.microedition.io.Connector;
import javax.microedition.io.StreamConnection;
import javax.microedition.io.StreamConnectionNotifier;


/**
 * Established the BT service, accepts connections
 * and send the requested item silently.
 *
 * @version ,
 */
final public class BTVideoServer implements Runnable {
    /** Describes this server */
    private static final UUID SERVER_UUID =
        new UUID("F0E0D0C0B0A000908070605040302012", false);
    
        /** The attribute id of the record item with item names. */
    private static final int VIDEO_NAMES_ATTRIBUTE_ID = 0x4322;

    /** Keeps the local device reference. */
    private LocalDevice localDevice;

    /** Accepts new connections. */
    private StreamConnectionNotifier notifier;

    /** Keeps the information about this server. */
    private ServiceRecord record;

    /** Keeps the parent reference to process specific actions. */
    //private MediaNet parent;

    /** Becomes 'true' when this component is finalized. */
    private boolean isClosed;

    /** Creates notifier and accepts clients to be processed. */
    private Thread accepterThread;

    /** Process the particular client from queue. */
    private ClientProcessor processor;

    /** Optimization: keeps the table of data elements to be published. */
    private final Hashtable dataElements = new Hashtable();
    
    public static boolean BTReady = false;

    /**
     * Constructs the bluetooth server, but it is initialized
     * in the different thread to "avoid dead lock".
     */
    public BTVideoServer(MediaNet parent) {
        //this.parent = parent;

        // we have to initialize a system in different thread...
        accepterThread = new Thread(this);
        accepterThread.start();
    }

    public BTVideoServer() {
        // we have to initialize a system in different thread...
        accepterThread = new Thread(this);
        accepterThread.start();
    }
 
    public static boolean isBTReady(){
        return BTReady;
    }
    
    /**
     * Accepts a new client and sends them the requested item.
     */
    public void run() {
        BTReady = false;

        try {
            // create/get a local device
            localDevice = LocalDevice.getLocalDevice();

            // set we are discoverable
            if (!localDevice.setDiscoverable(DiscoveryAgent.GIAC)) {
                // Some implementations always return false, even if
                // setDiscoverable successful
                // throw new IOException("Can't set discoverable mode...");
            }

            // prepare a URL to create a notifier
            StringBuffer url = new StringBuffer("btspp://");

            // indicate this is a server
            url.append("localhost").append(':');

            // add the UUID to identify this service
            url.append(SERVER_UUID.toString());

            // add the name for our service
            url.append(";name=Server");

            // request all of the client not to be authorized
            // some devices fail on authorize=true
            url.append(";authorize=false");

            // create notifier now
            notifier = (StreamConnectionNotifier)Connector.open(url.toString());

            // and remember the service record for the later updates
            record = localDevice.getRecord(notifier);

            // create a special attribute with item names
            DataElement base = new DataElement(DataElement.DATSEQ);
            record.setAttributeValue(VIDEO_NAMES_ATTRIBUTE_ID, base);

            // remember we've reached this point.
            BTReady = true;
        } catch (Exception e) {
            System.err.println("Can't initialize bluetooth: " + e);
        }
        
        try{
            //parent.completeInitialization(BTReady);
        }catch(Exception e){
            
        }

        // nothing to do if no bluetooth available
        if (!BTReady) {
            return;
        }
        
        setupSharedItems();
        
        // ok, start processor now
        processor = new ClientProcessor();

        // ok, start accepting connections then
        while (!isClosed) {
            StreamConnection conn = null;

            try {
                conn = notifier.acceptAndOpen();
            } catch (IOException e) {
                // wrong client or interrupted - continue anyway
                continue;
            }

            processor.addConnection(conn);
        }
    }

    /**
     * Updates the service record with the information
     * about the published items availability.
     * <p>
     * This method is invoked after the caller has checked
     * already that the real action should be done.
     *
     * @return true if record was updated successfully, false otherwise.
     */
    boolean changeInfo(String name, int attr_id,boolean is_published) {
        // ok, get the record from service
        DataElement base = record.getAttributeValue(attr_id);

        // check the corresponding DataElement object is created already
        DataElement de = (DataElement)dataElements.get(name);

        // if no, then create a new DataElement that describes this item
        if (de == null) {
            de = new DataElement(DataElement.STRING, name);
            dataElements.put(name, de);
        }

        // we know this data element has DATSEQ type
        if (is_published) {
            base.addElement(de);
        } else {
            if (!base.removeElement(de)) {
                System.err.println("Error: item was not removed for: " + name);

                return false;
            }
        }

        record.setAttributeValue(attr_id, base);

        try {
            localDevice.updateRecord(record);
        } catch (ServiceRegistrationException e) {
            System.err.println("Can't update record now for: " + name);

            return false;
        }

        return true;
    }

    /**
     * Destroy a work with bluetooth - exits the accepting
     * thread and close notifier.
     */
    void destroy() {
        isClosed = true;

        // finalize notifier work
        if (notifier != null) {
            try {
                notifier.close();
            } catch (IOException e) {
            } // ignore
        }

        // wait for acceptor thread is done
        try {
            accepterThread.join();
        } catch (InterruptedException e) {
        } // ignore

        // finalize processor
        if (processor != null) {
            processor.destroy(true);
        }

        processor = null;
    }

    /**
     * Reads the name from the specified connection
     * and sends this data through this connection, then
     * close it after all.
     */
    private void processConnection(StreamConnection conn) {
        // read the name first
        String name = readName(conn);

        // check this item is published and get the file name
        name = app_files.files.AccessControl.getFileName(name);

        // load data into buffer to be sent
        byte[] data = getData(name);

        // send data now
        sendData(data, conn);

        // close connection and good-bye
        try {
            conn.close();
        } catch (IOException e) {
        } // ignore
    }

    /** Send data. */
    private void sendData(byte[] data, StreamConnection conn) {
        if (data == null) {
            return;
        }

        OutputStream out = null;

        try {
            out = conn.openOutputStream();
            out.write(data.length >> 8);
            out.write(data.length & 0xff);
            out.write(data);
            out.flush();
        } catch (IOException e) {
            System.err.println("Can't send data: " + e);
        }

        // close output stream anyway
        if (out != null) {
            try {
                out.close();
            } catch (IOException e) {
            } // ignore
        }
    }

    /** Reads item name from specified connection. */
    private String readName(StreamConnection conn) {
        String imgName = null;
        InputStream in = null;

        try {
            in = conn.openInputStream();

            int length = in.read(); // 'name' length is 1 byte

            if (length <= 0) {
                throw new IOException("Can't read name length");
            }

            byte[] nameData = new byte[length];
            length = 0;

            while (length != nameData.length) {
                int n = in.read(nameData, length, nameData.length - length);

                if (n == -1) {
                    throw new IOException("Can't read name data");
                }

                length += n;
            }

            imgName = new String(nameData);
        } catch (IOException e) {
            System.err.println(e);
        }

        // close input stream anyway
        if (in != null) {
            try {
                in.close();
            } catch (IOException e) {
            } // ignore
        }

        return imgName;
    }

    /** Reads itemss data into a byte array. */
    private byte[] getData(String name) {
        if (name == null) {
            return null;
        }

        InputStream in = getClass().getResourceAsStream(name);

        // read data and create a byte array
        byte[] buff = new byte[1024];
        ByteArrayOutputStream baos = new ByteArrayOutputStream(1024);

        try {
            while (true) {
                int length = in.read(buff);

                if (length == -1) {
                    break;
                }

                baos.write(buff, 0, length);
            }
        } catch (IOException e) {
            System.err.println("Can't get data: name=" + name + " :" + e);

            return null;
        }

        return baos.toByteArray();
    }
    
    private void setupSharedItems() {
        Vector files = app_files.xml.ReadFile.getFileDetails("video");
        for (int n = 0; n < files.size(); n++){
            File file = (File) files.elementAt(n);
            changeInfo(file.getName(), VIDEO_NAMES_ATTRIBUTE_ID,true);
        }
    }

    /**
     * Organizes the queue of clients to be processed,
     * processes the clients one by one until destroyed.
     */
    private class ClientProcessor implements Runnable {
        private Thread processorThread;
        private Vector queue = new Vector();
        private boolean isOk = true;

        ClientProcessor() {
            processorThread = new Thread(this);
            processorThread.start();
        }

        public void run() {
            while (!isClosed) {
                // wait for new task to be processed
                synchronized (this) {
                    if (queue.size() == 0) {
                        try {
                            wait();
                        } catch (InterruptedException e) {
                            System.err.println("Unexpected exception: " + e);
                            destroy(false);

                            return;
                        }
                    }
                }

                // send the data to specified connection
                StreamConnection conn;

                synchronized (this) {
                    // may be awaked by "destroy" method.
                    if (isClosed) {
                        return;
                    }

                    conn = (StreamConnection)queue.firstElement();
                    queue.removeElementAt(0);
                    processConnection(conn);
                }
            }
        }

        /** Adds the connection to queue and notifies the thread. */
        void addConnection(StreamConnection conn) {
            synchronized (this) {
                queue.addElement(conn);
                notify();
            }
        }

        /** Closes the connections and . */
        void destroy(boolean needJoin) {
            StreamConnection conn;

            synchronized (this) {
                notify();

                while (queue.size() != 0) {
                    conn = (StreamConnection)queue.firstElement();
                    queue.removeElementAt(0);

                    try {
                        conn.close();
                    } catch (IOException e) {
                    } // ignore
                }
            }

            // wait until dispatching thread is done
            try {
                processorThread.join();
            } catch (InterruptedException e) {
            } // ignore
        }
    }
} // end of class 'BTImageServer' definition
